/*
 * Copyright 2013 Rivaldo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jety.server.transport;

import com.google.gson.JsonObject;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import org.jety.commons.domain.HandlerManager;
import org.jety.commons.domain.JsonRpcMethod;
import org.jety.commons.util.AsynchronousUtil;
import org.jety.commons.util.CommunicationUtil;
import org.jety.commons.util.JsonConverter;
import org.jety.commons.util.JsonRpcErrorBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Rivaldo
 */
public class HttpServerHandler implements HttpHandler {

    private final HandlerManager handlerManager;

    private static final int BUFF_LENGTH = 1024;
    private static final Logger LOG = LoggerFactory.getLogger(HttpServerHandler.class);

    public HttpServerHandler() {
        this.handlerManager = new HandlerManager();
    }

    /**
     * Add a new handler class to treat the client request.
     * @param <T> the handler class type
     * @param name a name to identfy the handler
     * @param handler an instance of the handle
     * @param classes the interfaces implemented by the handler
     * @throws RuntimeException erro while creating the handler
     */
    public <T> void addHandler(String name, T handler, Class<T>... classes) throws RuntimeException {
        handlerManager.addHandler(name, handler, classes);
    }

    public void handle(HttpExchange exchange) throws IOException {
        execute(exchange);
    }

    /**
     * Executes a request from the client.
     * @param exchange the transport object containing the request
     */
    public void execute(HttpExchange exchange) {

        handlerManager.lockHandlers();

        try {
            JsonObject req = readRequest(exchange);
            JsonRpcMethod method = CommunicationUtil.getRequestMethod(req);
            Method handlerMethod = handlerManager.getHandlerMethod(method);

            if (AsynchronousUtil.isAsynchronous(handlerMethod)) {
                //
            } else {
                executeSync(method, handlerMethod, exchange);
            }

        } catch (Exception ex) {
            LOG.error("Error while executing a request.", ex);
        }
    }

    /**
     * Executes the requested method synchronously.
     * @param method the requested method coded in json
     * @param handlerMethod the java method handler
     * @param transport the transport object containing the request
     */
    private void executeSync(JsonRpcMethod method, Method handlerMethod, HttpExchange exchange) {
        try {
            Object[] parameters = method.getTypedParams(handlerMethod);
            Object result = handlerMethod.invoke(handlerManager.getHandler(method), parameters);
            JsonObject response = CommunicationUtil.createResponse(method.getRequestID(), result);

            sendSyncResponse(response, exchange);
        } catch (Exception ex) {
            LOG.error("Error while executing a synchronous request.", ex);
        }
    }

    private JsonObject readRequest(HttpExchange exchange) throws IOException {

        JsonObject request = null;
        try {
            String requestData = read(exchange);
            LOG.debug("JSON-RPC >>  {}", requestData);
            request = JsonConverter.toJsonObject(requestData);
        } catch (IOException ex) {
            sendRequestError(ex, exchange);
            throw ex;
        }

        return request;
    }

    /**
     * Send a synchronous response to the client.
     * @param response the response coded in json
     * @param transport the transport responsible for send the data
     * @throws IOException erro while sending the data
     */
    private void sendSyncResponse(JsonObject response, HttpExchange exchange) throws IOException {
        String responseData = response.toString();
        write(responseData, exchange);
    }

    //<editor-fold defaultstate="collapsed" desc="Leitura e escrita">
    private String read(HttpExchange exchange) throws IOException {

        InputStream in = null;

        try {
            in = exchange.getRequestBody();
            ByteArrayOutputStream bos = new ByteArrayOutputStream();

            byte[] buff = new byte[BUFF_LENGTH];
            int n;
            while ((n = in.read(buff)) > 0) {
                bos.write(buff, 0, n);
            }

            bos.flush();
            bos.close();

            return bos.toString();
        } finally {
            if (in != null) {
                in.close();
            }
        }
    }

    private void write(String responseData, HttpExchange exchange) throws UnsupportedEncodingException, IOException {

        byte[] data = responseData.getBytes();

        exchange.getResponseHeaders().add("Content-Type", "application/json");
        exchange.getResponseHeaders().add("Content-Length", Integer.toString(data.length));

        exchange.sendResponseHeaders(200, data.length);

        PrintWriter out = null;
        try {
            out = new PrintWriter(exchange.getResponseBody());
            out.write(responseData);
            out.flush();
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="Envio de erros">
    private void sendRequestError(Throwable exception, HttpExchange exchange) throws UnsupportedEncodingException, IOException {
        String error = JsonRpcErrorBuilder.getRequestParseError(exception);
        LOG.debug("JSON-RPC error <<  {}", error);
        write(error, exchange);
    }

    private void sendInvalidRequestError(int requestID, Throwable exception, HttpExchange exchange) throws UnsupportedEncodingException, IOException  {
        String error = JsonRpcErrorBuilder.getInvalidRequestError(requestID, exception);
        LOG.debug("JSON-RPC error <<  {}", error);
        write(error, exchange);
    }
    //</editor-fold>

}
